# -*- coding: utf-8 -*-

# MangerSain -- A French food additives application for mobile devices
#
# Copyright (C) 2009-2010 Valéry Febvre <vfebvre@easter-eggs.com>
# http://code.google.com/p/mangersain/
#
# This file is part of MangerSain.
#
# MangerSain is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# MangerSain is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
MangerSain -- A French food additives application for mobile devices
"""


import os, re, time
#import lxml.html
import sqlite3
import elementary

APP_VERSION = '1.0.1'
APP_NAME = 'MangerSain'
DATA_DIR = '/usr/share/mangersain/'
#DATA_DIR = '../data'

re_code = re.compile(r'^[Ee]?\d+[a-zA-Z]?$')

class DB(object):
    def __init__(self):
        db_path = os.path.join(DATA_DIR, "mangersain.db")
        self.cnx = sqlite3.connect(db_path)
        self.cnx.row_factory = sqlite3.Row

        self.cursor = self.cnx.cursor()
        self.cursor.executescript("""
            CREATE TABLE IF NOT EXISTS additifs (
                id         INTEGER PRIMARY KEY,
                code       TEXT,
                name       TEXT,
                danger     INT,
                toxicity   TEXT
            );
            """)

    def add(self, data):
        sql = """
            INSERT INTO additifs (name, code, danger, toxicity) VALUES (?, ?, ?, ?)
        """
        self.cursor.execute(sql, data)
        self.cnx.commit()

        return self.cursor.lastrowid

    def get(self, id = None):
        sql = """
            SELECT * FROM additifs WHERE id = ?
        """
        return self.cursor.execute(sql, (id,)).fetchone()

    def search(self, word):
        self.cursor.execute('PRAGMA temp_store = MEMORY')
        self.cursor.execute('PRAGMA synchronous = OFF')

        if not word:
            sql = """
                SELECT * FROM additifs
            """
            return self.cursor.execute(sql).fetchall()
        else:
            if re_code.match(word):
                if word[0] not in ('e', 'E'):
                    word = 'E%s' % word
                word = word.capitalize()
                sql = """
                    SELECT * FROM additifs WHERE code LIKE ?
                """
                return self.cursor.execute(sql, ('%s%%' % word,)).fetchall()
            else:
                word = unicode(word, 'utf-8')
                sql = """
                    SELECT * FROM additifs WHERE name LIKE ? OR toxicity LIKE ?
                """
                return self.cursor.execute(sql, ('%%%s%%' % word, '%%%s%%' % word,)).fetchall()

    def close(self):
        self.cnx.close()

db = DB()

#url = 'http://mangersain.medicalistes.org/additifs.php?v_tri=code&v_type=%25'
#html = lxml.html.parse(url).getroot()

#for tr in html.find_class('tpi')[1].xpath('tr'):
#    data = []
#    for td in tr.getchildren():
#        dtype = td.get('class')
#        if dtype in ['colNom', 'colCode', 'colDanger', 'colToxicite']:
#            if dtype == 'colToxicite':
#                # toxicity is in a textarea
#                datum = td.getchildren()[0].text
#            elif dtype == 'colDanger':
#                datum = {
#                    'OUI (!)': '3',
#                    'OUI':     '2',
#                    'Suspect': '1',
#                    'NON':     '0',
#                    '?':       '-1'
#                    }[td.text.strip()]
#            else:
#                datum = td.text
#            data.append(datum.strip() if datum else None)
#    if len(data) == 4:
#        db.add(data)


class Icon(elementary.Icon):
    def __init__(self, parent, image):
        elementary.Icon.__init__(self, parent)
        self.file_set(os.path.join(DATA_DIR, 'images', image))

#
# Search page
#

class SearchPage(object):
    def __init__(self, main):
        self.main = main

        # main box
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        self.label = elementary.Label(self.main.win)
        self.label.label_set('')
        self.label.size_hint_weight_set(0, 0)
        self.label.size_hint_align_set(0.5, 0.5)
        self.box.pack_end(self.label)
        self.label.show()

        # search box: search button + entry
        box_search = elementary.Box(self.main.win)
        box_search.horizontal_set(True)
        box_search.size_hint_weight_set(1, 0)
        box_search.size_hint_align_set(-1, 0)
        box_search.show()

        btn_search = elementary.Button(self.main.win)
        btn_search.icon_set(Icon(self.main.win, 'loupe.png'))
        btn_search.callback_clicked_add(self._search)
        box_search.pack_end(btn_search)
        btn_search.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, False)
        scroller.size_hint_weight_set(1.0, 1.0)
        scroller.size_hint_align_set(-1.0, -1.0)
        scroller.show()

        self.entry_search = elementary.Entry(self.main.win)
        self.entry_search.scale_set(2)
        self.entry_search.single_line_set(True)
        self.entry_search.size_hint_weight_set(1, 1)
        self.entry_search.size_hint_align_set(-1, -1)
        self.entry_search.callback_activated_add(self._search)
        scroller.content_set(self.entry_search)
        self.entry_search.show()

        box_search.pack_end(scroller)
        self.box.pack_end(box_search)

        # results list
        self.list = elementary.List(self.main.win)
        self.list.scale_set(.5)
        self.list.size_hint_weight_set(1.0, 1.0)
        self.list.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(self.list)
        self.list.show()
        self.list_selected_item = None
        self.list.callback_clicked_add(self._show)

        self.main.pager.content_push(self.box)

    def _on_list_item_selected(self, item, action, data):
        self.list_selected_item = data

    def _show(self, *args):
        additif = db.get(self.list_selected_item['id'])
        self.main.card_page.set(additif)
        self.main.show_card_page()

    def _search(self, *args):
        self.list.clear()
        self.label.label_set('')

        word = self.entry_search.entry_get().replace('<br>', '').strip()

        res = db.search(word if word != '' else None)

        if res:
            nb = len(res)
            self.label.label_set('<b>%d additif(s) trouvé(s)</>' % nb)
            for r in res:
                if r['danger'] >= 0:
                    icon = Icon(self.main.win, {
                        0: 'not-or-few-toxic.png',
                        1: 'suspect.png',
                        2: 'toxic.png',
                        3: 'very-toxic.png',
                        }[r['danger']])
                    icon.scale_set(1, 0)
                else:
                    icon = None

                label = '%s - %s' % (r['code'].encode('utf-8'), r['name'].encode('utf-8'))

                self.list.item_append(label, icon, None, self._on_list_item_selected, {'id': r['id']})

            self.list.go()
            if nb == 1:
                self.list_selected_item = {'id': res[0]['id']}
                self._show()
        else:
            self.label.label_set('<b>Aucun additif trouvé</>')

#
# Card page
#

class CardPage(object):
    def __init__(self, main):
        self.main = main

        # main box
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, True)
        scroller.size_hint_weight_set(1.0, 1.0)
        scroller.size_hint_align_set(-1.0, -1.0)
        scroller.show()

        self.entry = elementary.Entry(self.main.win)
        self.entry.editable_set(False)
        self.entry.line_wrap_set(True)
        self.entry.size_hint_weight_set(1, 1)
        self.entry.size_hint_align_set(-1, -1)
        self.entry.entry_set("<b>Aucun additif n'a encore été selectionné.</>")
        scroller.content_set(self.entry)
        self.entry.show()

        self.box.pack_end(scroller)

        self.main.pager.content_push(self.box)

    def set(self, additif):
        info = []

        info.append('<b>%s</>' % additif['code'].encode('utf-8'))

        info.append('<em>%s</>' % additif['name'].encode('utf-8'))

        toxicity = additif['toxicity'].encode('utf-8').replace('\n', '<br><br>').replace('\r', '')
        info.append(toxicity)

        self.entry.entry_set('<br><br>'.join(info))

#
# Preferences page
#

class PreferencesPage(object):
    def __init__(self, main):
        self.main = main
        self.fullscreen = False

        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        sc = elementary.Scroller(self.main.win)
        sc.bounce_set(False, False)
        sc.size_hint_weight_set(1.0, 1.0)
        sc.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(sc)
        sc.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1.0, 0)
        box.size_hint_align_set(-1, 0)
        sc.content_set(box)
        box.show()

        if hasattr(self.main.win, 'fullscreen_set'):
            self.tg_fullscreen = elementary.Toggle(self.main.win)
            self.tg_fullscreen.icon_set(Icon(self.main.win, 'fullscreen.png'))
            self.tg_fullscreen.label_set("Plein écran")
            self.tg_fullscreen.size_hint_weight_set(1, 0)
            self.tg_fullscreen.size_hint_align_set(-1, 0)
            self.tg_fullscreen.state_set(self.fullscreen)
            self.tg_fullscreen.states_labels_set("Oui", "Non")
            self.tg_fullscreen.callback_changed_add(self.toggle_fullscreen)
            box.pack_end(self.tg_fullscreen)
            self.tg_fullscreen.show()

        self.main.pager.content_push(self.box)

    def toggle_fullscreen(self, *args):
        state = self.tg_fullscreen.state_get()
        if state != self.fullscreen:
            self.main.win.fullscreen_set(state)
            self.fullscreen = state


#
# About page
#

class AboutPage(object):
    def __init__(self, main):
        self.main = main

        # main box
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        label = elementary.Label(self.main.win)
        label.label_set('<b>%s %s</>' % (APP_NAME, APP_VERSION))
        label.size_hint_weight_set(0, 0)
        label.size_hint_align_set(0.5, 0.5)
        self.box.pack_end(label)
        label.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, True)
        scroller.size_hint_weight_set(1.0, 1.0)
        scroller.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(scroller)
        scroller.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 1)
        box.size_hint_align_set(-1, -1)
        scroller.content_set(box)
        box.show()

        about  = "<b>MangerSain</b> vous permet de consulter simplement et rapidement une liste des additifs alimentaires et leur toxicité.<br><br>Les données proviennent du site <b>mangersain.medicalistes.org</>. Les informations sont établies à partir de sources récoltées et triées depuis d'autres sites dont les adresses se trouvent plus bas.<br><br>Copyright (c) 2009-2010 Valéry Febvre<br>License GNU GPL v3"
        about += "<br><br>========<br><br>"
        about += "Les additifs chimiques envahissent notre assiette...<br>Jusqu'à notre pain !<br><br>Bien que prétendus inoffensifs dans leurs dosages, aucun scientifique sérieux ne peut garantir qu'il en est de même après leur accumulation et leurs interactions au fil du temps !...<br><br><b>Leur toxicité est pourtant prouvée</>, mais la chimie alimentaire est un secteur économique vraisemblablement très rentable...<br><br>Les enjeux priment sur notre santé, discrètement.<br><br>Le pari des résistants, c'est avant-tout lire une étiquette !"
        about += "<br><br>========<br><br>"

        entry = elementary.Entry(self.main.win)
        entry.editable_set(False)
        entry.line_wrap_set(True)
        entry.size_hint_weight_set(1, 1)
        entry.size_hint_align_set(-1, -1)
        entry.entry_set(about)
        box.pack_end(entry)
        entry.show()

        links = [
            (
                'http://science-citoyen.u-strasbg.fr/dossiers/additifs',
                "[1] : <a href=\"%s\"><link>Site de l'université de Strasbourg</></a><br>Site sérieux et bien documenté.",
                ),
            (
                'http://www.ecovie.info/index.php?to=1&so=3&page=1',
                "[2] : Site du Dr.Seror<br>Site très référencé sur Internet.<br>Note août 2007 : Page supprimée sous la pression des fabriquants de colorants.<br>Des copies restent disponibles sur internet (<a href=\"%s\"><link>ici</></a> entre autres)."
                ),
            (
                None,
                "[3] : Le portail (suisse) romand des thérapies naturelles et alternatives<br>Note jan.2007 : ce site a été refait et la section des additifs supprimée."
                ),
            (
                'http://sm.coppier.free.fr/additifs/index.php3',
                "[4] : <a href=\"%s\"><link>Le guide des additifs alimentaires</></a><br>Site privé. Abondamment référencé sur Internet."
                ),
            (
                None,
                "[5] : membres.lycos.fr/resister/additifs<br>Note jan.2007 : le site a été refait et la section des additifs supprimée."
                ),
            (
                'http://www.quechoisir.org/',
                "[6] : <a href=\"%s\"><link>UFC : Union Fédérale des Consommateurs</></a><br>Taper 'liste des additifs alimentaires' dans la zone 'Rechercher'.<br>Documentation limitée."
                ),
            (
                'http://www.achats-industriels.com/matieres/colorants3.asp',
                "[7] : <a href=\"%s\"><link>Annuaire des acheteurs de l'industrie</></a><br>Documentation limitée."
                ),
            (
                None,
                "[8] : 1001cocktails<br>Note jan.2007 : La section additifs a été supprimée."
                ),
            (
                None,
                "[9] : 'Du poison dans votre assiette ?' de Cécile Voss. Ed. VSZ. ISBN 2-930241-18-7<br>Guide pratique de TEST-ACHATS (Belgique).<br>Très bon ouvrage."
                ),
            (
                'http://www.les-additifs-alimentaires.com/liste-tous-loi.php',
                "[10] : <a href=\"%s\"><link>Moktoipas</></a><br>Site privé. Possibilité de commenter des additifs."
                ),
            (
                'http://www.foodfigures.com',
                "[11] : <a href=\"%s\"><link>Foodfigures.com</></a><br>Site en anglais ajouté le 11-08-2007, informations claires et très accessibles."
                ),
            (
                'http://mbm.net.au/health/guide.htm',
                "[12] : <a href=\"%s\"><link>Mutual Benefit Marketing</></a><br>Site en anglais ajouté le 2-01-2008, informations pertinentes, les additifs sont accessibles<br>par familles, en descendant le menu gauche."
                )
            ]
        for link in links:
            anchor = elementary.AnchorBlock(self.main.win)
            anchor.size_hint_weight_set(1, 1)
            anchor.size_hint_align_set(-1, -1)
            if link[0]:
                anchor.text_set(link[1] % link[0] + '<br><br>')
                anchor.callback_clicked_add(self._on_anchor_clicked, link[0])
            else:
                anchor.text_set(link[1] + '<br><br>')
            box.pack_end(anchor)
            anchor.show()

        self.main.pager.content_push(self.box)

    def _on_anchor_clicked(self, object, event, url):
        if url:
            msg = 'Voulez-vous réellement ouvrir ce lien?'
            confirm_dlg.open(msg, self._open_url, url = url)

    def _open_url(self, url):
        import subprocess

        cmd = None
        if os.path.exists("/usr/bin/midori"):
            cmd = ["/usr/bin/midori"]
        elif os.path.exists("/usr/bin/dillo"):
            cmd = ["/usr/bin/dillo"]
        elif os.path.exists("/usr/bin/xdg-open"):
            cmd = ["/usr/bin/xdg-open"]
        if cmd:
            cmd.append(url)
            subprocess.Popen(cmd)


#
# Confirm dialog
#

class ConfirmDialog(object):
    def __init__(self, title = 'Confirmation ?'):
        self.win = None
        self.title = title

    def _create(self):
        self.win = elementary.Window(self.title, elementary.ELM_WIN_BASIC)
        self.win.title_set(self.title)
        self.win.callback_destroy_add(self._close)

        bg = elementary.Background(self.win)
        self.win.resize_object_add(bg)
        bg.size_hint_weight_set(1.0, 1.0)
        bg.show()

        box = elementary.Box(self.win)
        box.size_hint_weight_set(1, 1)
        box.size_hint_align_set(-1, -1)
        box.show()

        table = elementary.Table(self.win)
        table.homogenous_set(True)
        table.size_hint_weight_set(1, 1)
        table.size_hint_align_set(-1, -1)

        label = elementary.Label(self.win)
        table.pack(label, 0, 0, 1, 1)

        self.entry_title = elementary.Entry(self.win)
        self.entry_title.line_wrap_set(True)
        self.entry_title.editable_set(False)
        self.entry_title.entry_set('')
        self.entry_title.size_hint_weight_set(1, 1)
        table.pack(self.entry_title, 1, 1, 1, 1)
        self.entry_title.show()

        label = elementary.Label(self.win)
        table.pack(label, 2, 2, 1, 1)

        box.pack_end(table)
        table.show()

        box_actions = elementary.Box(self.win)
        box_actions.horizontal_set(True)
        box_actions.homogenous_set(True)
        box_actions.size_hint_weight_set(1, 0)
        box_actions.size_hint_align_set(-1, -1)
        box_actions.show()
        box.pack_end(box_actions)

        btn_yes = elementary.Button(self.win)
        btn_yes.label_set("Oui")
        btn_yes.icon_set(Icon(self.win, 'yes.png'))
        btn_yes.size_hint_weight_set(1.0, 1.0)
        btn_yes.size_hint_align_set(-1.0, -1.0)
        btn_yes.callback_clicked_add(self._on_btn_yes_clicked)
        box_actions.pack_end(btn_yes)
        btn_yes.show()

        btn_no = elementary.Button(self.win)
        btn_no.label_set("Non")
        btn_no.icon_set(Icon(self.win, 'close.png'))
        btn_no.size_hint_weight_set(1.0, 1.0)
        btn_no.size_hint_align_set(-1.0, -1.0)
        btn_no.callback_clicked_add(self._close)
        box_actions.pack_end(btn_no)
        btn_no.show()

        self.win.resize_object_add(box)

    def open(self, message, confirm_cb, **kwargs):
        if not self.win:
            self._create()

        self.entry_title.entry_set(message)
        self.confirm_cb = confirm_cb
        self.confirm_cb_args = kwargs
        self.win.resize(480, 480)
        self.win.show()

    def _on_btn_yes_clicked(self, *args):
        self._close()
        self.confirm_cb(**self.confirm_cb_args)

    def _close(self, *args):
        self.win.hide()

confirm_dlg = ConfirmDialog()


class MangerSain(object):
    def __init__(self, verbose=False):
        self.verbose = verbose
        self.win = elementary.Window(APP_NAME, elementary.ELM_WIN_BASIC)
        self.win.title_set(APP_NAME)
        self.win.callback_destroy_add(self.quit)

        bg = elementary.Background(self.win)
        self.win.resize_object_add(bg)
        bg.size_hint_weight_set(1, 1)
        bg.show()

        box = elementary.Box(self.win)
        self.win.resize_object_add(box)
        box.size_hint_weight_set(1, 1)
        box.show()

        # HACK HERE: put toolbar in a box to be able to scale it (75%)
        box_toolbar = elementary.Box(self.win)
        box_toolbar.scale_set(.75)
        box_toolbar.size_hint_weight_set(1.0, 0)
        box_toolbar.size_hint_align_set(-1.0, 0)
        box_toolbar.show()

        toolbar = elementary.Toolbar(self.win)
        toolbar.size_hint_align_set(-1.0, 0)
        toolbar.show()

        self.toolbar_item_search = toolbar.item_add(
            Icon(self.win, 'search.png'), "Recherche", self.show_search_page)
        self.toolbar_item_card = toolbar.item_add(
            Icon(self.win, 'card.png'), "Fiche", self.show_card_page)
        self.toolbar_item_preferences = toolbar.item_add(
            Icon(self.win, 'preferences.png'), "Préférences", self.show_preferences_page)
        self.toolbar_item_about = toolbar.item_add(
            Icon(self.win, 'about.png'), "À propos", self.show_about_page)

        box_toolbar.pack_end(toolbar)
        box.pack_end(box_toolbar)

        self.pager = elementary.Pager(self.win)
        self.pager.size_hint_weight_set(1.0, 1.0)
        self.pager.size_hint_align_set(-1.0, -1.0)
        box.pack_end(self.pager)
        self.pager.show()

        self.search_page = SearchPage(self)
        self.preferences_page = PreferencesPage(self)
        self.card_page = CardPage(self)
        self.about_page = AboutPage(self)
        self.toolbar_item_search.select()

        self.win.resize(480, 640)
        self.win.show()

    def quit(self, *args):
        if self.verbose:
            print "Bye bye, thx to use %s" % APP_NAME
        db.close()
        elementary.exit()

    def show_search_page(self, *args):
        self.pager.content_promote(self.search_page.box)

    def show_card_page(self, *args):
        self.toolbar_item_card.select()
        self.pager.content_promote(self.card_page.box)

    def show_preferences_page(self, *args):
        self.pager.content_promote(self.preferences_page.box)

    def show_about_page(self, *args):
        self.pager.content_promote(self.about_page.box)


def main():
    elementary.init()
    MangerSain()
    elementary.run()
    elementary.shutdown()
    return 0

if __name__ == "__main__":
    exit(main())
